/*
 * Copyright (c) 2018-2024 北京华恒盛世科技有限公司
 * QUICKPOOL is licensed under Mulan PSL v2.
 * You can use this software according to the terms and conditions of the Mulan PSL v2.
 * You may obtain a copy of Mulan PSL v2 at:
 *     http://license.coscl.org.cn/MulanPSL2
 * THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
 * EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
 * MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 * See the Mulan PSL v2 for more details.
*/

#include <netdb.h>
#include <pthread.h>
#include <sys/socket.h>
#include <arpa/inet.h>

#include "hash.h"
#include "common.h"

#define MAX_HOSTALIAS 64

static HASH_TABLE_T *shashHostName;
static HASH_TABLE_T *shashHostaddr;
static char sstrHostFile[PATH_MAX];

static pthread_mutex_t mutexHostEnt = PTHREAD_MUTEX_INITIALIZER;

static int create_host_table(void);
static void install_host2table(const char *, struct hostent *);

static void strip_domain(char *);

struct addrinfo *
gf_hostname(const char *host, int port)
{
    struct addrinfo hints;
    memset(&hints, 0, sizeof(struct addrinfo));
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_flags = AI_PASSIVE;
    hints.ai_protocol = 0;
    hints.ai_canonname = NULL;
    hints.ai_addr = NULL;
    hints.ai_next = NULL;

    char strPort[13];
    snprintf(strPort, 12, "%d", port);

    int cc;
    struct addrinfo *result;
    cc = getaddrinfo(host, strPort, &hints, &result);
    if (cc != 0) {
        log_message(ERR, NOCLASS, "%s: getaddrinfo(%s:%d) failed: %s.",
                    __func__, host, port, gai_strerror(cc));
        return NULL;
    }

    return result;
}

/* 
 */
struct hostent *
gf_gethostbyname(const char *hname)
{
    char quickpoolHostName[MAXHOSTNAMELEN+1];

    if (strlen(hname) >= MAXHOSTNAMELEN) {
        log_message(ERR, NOCLASS, "%s: input host[%s] is longer than %d.", hname, MAXHOSTNAMELEN);
        return NULL;
    }

    strncpy(quickpoolHostName, hname, MAXHOSTNAMELEN);
    strip_domain(quickpoolHostName);

    /* This is always somewhat controversial
     * should we have an explicit libray init
     * call host_cache_init() or doing the
     * initialization as part of the first call...
     */
    if (NULL == shashHostName) {
        create_host_table();
    }

    struct hostent *entry, hp;
    int cc;
    entry = (struct hostent *)gf_hash_find(shashHostName, quickpoolHostName);
    if (NULL != entry) {
        return entry;
    }

    pthread_mutex_lock(&mutexHostEnt);
    
    struct hostent *result;
    int rc;
    char officialName[4*MAXHOSTNAMELEN];
    cc = gethostbyname_r(quickpoolHostName, &hp, officialName, 4*MAXHOSTNAMELEN, &result, &rc);
    if (0 != cc || NULL == result) {
        log_message(ERR, NOCLASS, "%s: gethostbyname(%s) failed: %s.",
                    __func__, quickpoolHostName, hstrerror(rc));
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    strip_domain(hp.h_name);

    entry = (struct hostent *)gf_hash_find(shashHostName, hp.h_name);
    if (NULL != entry) {
        pthread_mutex_unlock(&mutexHostEnt);
        return entry;
    }

    entry = (struct hostent *)calloc(1, sizeof(struct hostent));
    if (NULL == entry) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                    __func__, sizeof(struct hostent));
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    strip_domain(hp.h_name);

    entry->h_name = copy_string(hp.h_name);
    entry->h_addrtype = hp.h_addrtype;
    entry->h_length = hp.h_length;

    cc = 0;
    while (hp.h_aliases[cc]) {
        cc ++;
    }

    entry->h_aliases = (char **)calloc(cc+1, sizeof(char *));
    if (NULL == entry->h_aliases) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                    __func__, (cc+1)*sizeof(char *));
        FREEUP(entry);
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    cc = 0;
    while (hp.h_aliases[cc]) {
        entry->h_aliases[cc] = copy_string(hp.h_aliases[cc]);
        cc ++;
    }
    entry->h_aliases[cc] = NULL;

    cc = 0;
    while (hp.h_addr_list[cc]) {
        cc ++;
    }

    entry->h_addr_list = (char **)calloc(cc+1, sizeof(char *));
    if (NULL == entry->h_addr_list) {
        log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                    __func__, (cc+1)*sizeof(char *));
        FREEUP(entry);
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    cc = 0;
    while (hp.h_addr_list[cc]) {
        entry->h_addr_list[cc] = (char*) calloc(1, sizeof(in_addr_t) + 1);
        if (NULL == entry->h_addr_list[cc]) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, sizeof(in_addr_t) + 1);
            FREEUP(entry);
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }
        memcpy(entry->h_addr_list[cc], hp.h_addr_list[cc], sizeof(in_addr_t));
        cc ++;
    }
    entry->h_addr_list[cc] = NULL;

    /* add the new host to the host hash table */
    install_host2table(entry->h_name, entry);

    pthread_mutex_unlock(&mutexHostEnt);

    return entry;
}

/* 
 */
struct hostent *
gf_gethostbyaddr(in_addr_t *addr, socklen_t len, int type)
{
    char *ipbuf;

    if (NULL == shashHostName) {
        create_host_table();
    }

    pthread_mutex_lock(&mutexHostEnt);
    ipbuf = inet_ntoa(*(struct in_addr *)addr);

    struct hostent *entry, hp;
    int cc;
    entry = (struct hostent *)gf_hash_find(shashHostaddr, ipbuf);
    if (NULL != entry) {
        pthread_mutex_unlock(&mutexHostEnt);
        return entry;
    }

    struct hostent *result;
    int rc;
    char officialName[4*MAXHOSTNAMELEN];
    cc = gethostbyaddr_r(addr, len, type, &hp, officialName, 4*MAXHOSTNAMELEN, &result, &rc);
    if (0 != cc) {
        log_message(ERR, NOCLASS, "%s: gethostaddr(%s) failed: %s.", __func__, ipbuf, hstrerror(rc));
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    if (NULL == result) {
        FREEUP(entry);
        pthread_mutex_unlock(&mutexHostEnt);
        return NULL;
    }

    strip_domain(hp.h_name);

    entry = (struct hostent *)gf_hash_find(shashHostName, hp.h_name);
    if (NULL == entry) {

        entry = (struct hostent *)calloc(1, sizeof(struct hostent *));
        if (NULL == entry) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, sizeof(struct hostent));
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }

        entry->h_name = copy_string(hp.h_name);
        entry->h_addrtype = hp.h_addrtype;
        entry->h_length = hp.h_length;

        cc = 0;
        while (hp.h_aliases[cc]) {
            cc ++;
        }

        entry->h_aliases = (char **)calloc(cc+1, sizeof(char *));
        if (NULL == entry->h_aliases) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, (cc+1)*sizeof(char *));
            FREEUP(entry);
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }

        cc = 0;
        while (hp.h_aliases[cc]) {
            entry->h_aliases[cc] = copy_string(hp.h_aliases[cc]);
            cc ++;
        }
        entry->h_aliases[cc] = NULL;

        cc = 0;
        while (hp.h_addr_list[cc]) {
            cc ++;
        }

        entry->h_addr_list = (char **)calloc(cc+1, sizeof(char *));
        if (NULL == entry->h_addr_list) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, (cc+1)*sizeof(char *));
            FREEUP(entry);
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }

        cc = 0;
        while (hp.h_addr_list[cc]) {
            entry->h_addr_list[cc] = (char*) calloc(1, sizeof(in_addr_t) + 1);
            if (NULL == entry->h_addr_list[cc]) {
                log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                            __func__, sizeof(in_addr_t) + 1);
                FREEUP(entry);
                pthread_mutex_unlock(&mutexHostEnt);
                return NULL;
            }
            memcpy(entry->h_addr_list[cc], hp.h_addr_list[cc], sizeof(in_addr_t));
            cc ++;
        }
        entry->h_addr_list[cc] = NULL;

        install_host2table(hp.h_name, entry);
    } else {
        cc = 0;
        while (entry->h_aliases[cc] != NULL && cc < MAX_HOSTALIAS) {
            FREEUP(entry->h_aliases[cc]);
            cc ++;
        }
        FREEUP(entry->h_aliases);

        cc = 0;
        while (hp.h_aliases[cc]) {
            cc ++;
        }

        entry->h_aliases = (char **)calloc(cc+1, sizeof(char *));
        if (NULL == entry->h_aliases) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, (cc+1)*sizeof(char *));
            FREEUP(entry);
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }

        cc = 0;
        while (hp.h_aliases[cc]) {
            entry->h_aliases[cc] = copy_string(hp.h_aliases[cc]);
            cc ++;
        }
        entry->h_aliases[cc] = NULL;

        cc = 0;
        while (entry->h_addr_list[cc]) {
            FREEUP(entry->h_addr_list[cc]);
            cc ++;
        }
        FREEUP(entry->h_addr_list);

        cc = 0;
        while (hp.h_addr_list[cc]) {
            cc ++;
        }

        entry->h_addr_list = (char **)calloc(cc+1, sizeof(char *));
        if (NULL == entry->h_addr_list) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                        __func__, (cc+1)*sizeof(char *));
            FREEUP(entry);
            pthread_mutex_unlock(&mutexHostEnt);
            return NULL;
        }

        cc = 0;
        while (hp.h_addr_list[cc]) {
            entry->h_addr_list[cc] = (char*) calloc(1, sizeof(in_addr_t) + 1);
            if (NULL == entry->h_addr_list[cc]) {
                log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                            __func__, sizeof(in_addr_t) + 1);
                FREEUP(entry);
                pthread_mutex_unlock(&mutexHostEnt);
                return NULL;
            }
            memcpy(entry->h_addr_list[cc], hp.h_addr_list[cc], sizeof(in_addr_t));
            cc ++;
        }
        entry->h_addr_list[cc] = NULL;
    }

    pthread_mutex_unlock(&mutexHostEnt);

    return entry;
}

void
gf_set_hostfile(const char *confDir)
{
    snprintf(sstrHostFile, PATH_MAX, "%s/hosts", confDir);

    return;
}

static int
create_host_table(void)
{
    FILE *fp;

    shashHostName = gf_hash_make(101);
    shashHostaddr = gf_hash_make(101);
    if (NULL == shashHostName || NULL == shashHostaddr) {
        return -1;
    }

    if ((fp = fopen(sstrHostFile, "r")) == NULL) {
        return -1;
    }

    unsigned int lineNum = 0;
    char buf[BUFSIZ];
    char *line;
    while (NULL != (line=gf_get_line(fp, &lineNum, buf))) {
        char *addrstr;
        char *token;
        unsigned int len = strlen(line);

        char *sp = line;
        addrstr = (char *)calloc(len+1, sizeof(char));
        if (NULL == addrstr) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.", __func__, len+1);
            fclose(fp);
            return -1;
        }

        token = get_string_token(&sp, " ", addrstr, len+1);
        if (token == NULL) {
            FREEUP(addrstr);
            continue;
        }

        len = strlen(sp);

        char *name;
        name = (char *)calloc(len+1, sizeof(char));
        if (NULL == name) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.", __func__, len+1);
            fclose(fp);
            return -1;
        }

        token = get_string_token(&sp, " ", name, len+1);
        if (token == NULL) {
            FREEUP(addrstr);
            FREEUP(name);
            continue;
        }
        if (name[len-2] == '\n') {
            name[len-2] = '\0';
        }
        strip_domain(name);

        static char *alias[MAX_HOSTALIAS];
        memset(alias, 0, sizeof(char *) * MAX_HOSTALIAS);

        char *others;
        others = (char *)calloc(len+1, sizeof(char));
        if (NULL == others) {
            log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.", __func__, len+1);
            fclose(fp);
            return -1;
        }

        unsigned int cc = 0;
        len = strlen(sp);
        while (NULL != (token = get_string_token(&sp, " ", others, len+1)) && cc < MAX_HOSTALIAS) {
            if (token[len-2] == '\n') {
                token[len-2] = '\0';
            }
            alias[cc] = copy_string(token);
            ++cc;
            len = strlen(sp);
        }
        FREEUP(others);

        /* multihomed hosts are listed multiple times in the host file each time
         * with the same name but different addr.
         *
         * 192.168.7.1 jumbo
         * 192.168.7.4 jumbo
         *     ...
         */
        struct hostent *entry = (struct hostent *)gf_hash_find(shashHostName, name);
        if (NULL == entry) {
            struct in_addr addr;
            addr.s_addr = inet_addr(addrstr);
            if (addr.s_addr == INADDR_NONE) {
                //The IP address is invalid;
                continue;
            }
            
            entry = (struct hostent *)calloc(1, sizeof(struct hostent));
            if (NULL == entry) {
                log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                            __func__, sizeof(struct hostent));
                fclose(fp);
                return -1;
            }

            entry->h_name = name;
            entry->h_length = 4;
            entry->h_addrtype = AF_INET;
            entry->h_aliases = alias;
            entry->h_addr_list = (char **)calloc(2, sizeof(char *));
            if (NULL == entry->h_addr_list) {
                log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                            __func__, 2*sizeof(char *));
                FREEUP(entry);
                fclose(fp);
                return -1;
            }

            entry->h_addr_list[0] = (char *)calloc(1, sizeof(struct in_addr));
            if (NULL == entry->h_addr_list[0]) {
                log_message(ERR, NOCLASS, "%s: calloc(%d) failed due to %m.",
                            __func__, sizeof(struct in_addr));
                FREEUP(entry);
                fclose(fp);
                return -1;
            }

            entry->h_addr_list[1] = NULL;

            inet_aton(addrstr, (struct in_addr *)entry->h_addr_list[0]);            
            install_host2table(name, entry);

            continue;
        }

        bool_t find = FALSE;
        cc = 0;
        while (entry->h_addr_list[cc]) {
            cc++;
        }

        unsigned int numAddr = cc;
        cc = 0;
        while (entry->h_addr_list[cc]) {
            if (strcmp(entry->h_addr_list[cc], addrstr) == 0) {
                find = TRUE;
                break;
            }
            cc ++;
        }

        FREEUP(name);
        FREEUP(addrstr);

        cc = 0;
        while (alias[cc] != NULL && cc < MAX_HOSTALIAS) {
            FREEUP(alias[cc]);
            cc ++;
        }

        if (find) {
            continue;
        }

        entry->h_addr_list = (char **)realloc(entry->h_addr_list, (numAddr+1)*sizeof(char*));
        if (NULL == entry->h_addr_list) {
            log_message(ERR, NOCLASS, "%s: realloc(%d) failed due to %m.",
                        __func__, (numAddr+1)*sizeof(char *));
            FREEUP(entry);
            fclose(fp);
            return -1;
        }

        entry->h_addr_list[numAddr-1] = addrstr;
        entry->h_addr_list[numAddr] = NULL;
    } /* while() */

    fclose(fp);

    return 0;
}

static void
install_host2table(const char *hname, struct hostent *hp)
{
    /* add the host to the table by its name
     * if it exists already we must be processing
     * another ipaddr for it.
     */
    struct hostent *entry;
    entry = (struct hostent *)gf_hash_find(shashHostName, hname);
    if (NULL == entry) {
        gf_hash_install(shashHostName, hname, (void *)hp);
    }

    unsigned int cc = 0;
    while (hp->h_addr_list[cc]) {
        char ipbuf[30];

        /* now hash the host by its addr,
         * there can be N addrs but each
         * must be unique... */
        const char *ip;
        ip = inet_ntop(hp->h_addrtype, hp->h_addr_list[cc], ipbuf, 30);
        if (NULL == ip) {
            continue;
        }

        entry = (struct hostent *)gf_hash_find(shashHostaddr, ipbuf);
        /* If new is false it means this IP
         * is configured for another host already,
         * confusion is waiting down the road as
         * Gethostbyadrr_() will always return the
         * first configured host.
         * 192.168.1.4 joe
         * 192.168.1.4 banana
         * when banana will call the library will
         * always tell you joe called. */
        if (NULL == entry) {
            gf_hash_install(shashHostaddr, ipbuf, (void *)hp);
        }

        ++cc; /* nexte */
    }
}

/* 
 */
static void
strip_domain(char *name)
{
    char *p;

    if ((p = strchr(name, '.')))
        *p = 0;

    return;
}
